﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace Mesuro.Common {
	public delegate double VirtualChannelCallback( Vector source );
	public static class DataSet {

		private static string defaultSeriesKey = null;
		private static Dictionary<string, ISeries> dataset = new Dictionary<string, ISeries>();

		private static readonly DatasetChannelsObject _channels = new DatasetChannelsObject();
		public static IDatasetChannelsObject Channels {
			get { return DataSet._channels; }
		}

		public static bool UsePreferredChannels {
			get { return _channels.UsePreferredChannels; }
			set { _channels.UsePreferredChannels = value; }
		}

		public static ReadOnlyObservableCollection<IChannelRef> AllChannels { get { return _channels.AllChannels; } }

		public static IEnumerable<IChannelRef> PreferredChannels { get { return _channels.PreferredChannels; } }

		public static string CensorName( string name ) {
			if (String.IsNullOrWhiteSpace( name ))
				name = "UnnamedSeries";

			StringBuilder nameBuilder = new StringBuilder();

			IEnumerator<char> cenumr = name.GetEnumerator();
			cenumr.MoveNext();

			if (Char.IsLetter( cenumr.Current ))
				nameBuilder.Append( cenumr.Current );
			else
				nameBuilder.Append( '_' );

			while (cenumr.MoveNext())
				if (Char.IsLetterOrDigit( cenumr.Current ))
					nameBuilder.Append( cenumr.Current );
				else
					nameBuilder.Append( '_' );

			return nameBuilder.ToString();
		}

		public static void AddSeries( ISeries series, bool preferred = true ) {
			//Make sure the seires has a valid name
			series.Name = CensorName( series.Name );

			//If there is already a series with the same name, add a number to this series' name
			int i = 0;
			while (dataset.ContainsKey( series.Name + i )) {
				i++;
			}

			if (i > 0)
				series.Name += i;

			dataset.Add( series.Name, new VirtualisingSeries( series ) );

			if (defaultSeriesKey == null)
				defaultSeriesKey = series.Name;

			foreach (IChannel ch in series.Channels) {
				IChannelRef cr = new DataSetChannelRef( String.Format( "{0}.{1}", series.Name, ch.Name ) );
				_channels._allChannels.Add( cr );
				SetPreferred( cr, preferred );
			}
			//if (CollectionChanged != null)
			//    CollectionChanged( this, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, ) );
		}

		public static void RegisterVirtualChannel( ISeries basedOn, string name, VirtualChannelCallback callback, Limits? limits = null ) {
			ISeries s = dataset[basedOn.Name];
			name = CensorName( name );

			VirtualisingSeries vs = (s as VirtualisingSeries) ?? new VirtualisingSeries( s );
			VirtualChannel vc = new VirtualChannel( name, callback, limits );
			vs.AddChannel( vc );

			IChannelRef cr = new DataSetChannelRef( String.Format( "{0}.{1}", vs.Name, name ) );
			_channels._allChannels.Add( cr );
			_channels._preferredChannels.Add( cr );

			dataset[s.Name] = vs;
		}

		public static bool IsPreferred( IChannelRef cr ) {
			return _channels._preferredChannels.Contains( cr );
		}
		public static void SetPreferred( IChannelRef cr, bool preferred ) {
			if (!_channels._allChannels.Contains( cr ))
				throw new InvalidOperationException( "Dataset does not contain specified channel." );
			if (preferred)
				_channels._preferredChannels.Add( cr );
			else
				_channels._preferredChannels.Remove( cr );
		}

		public static IChannel GetChannel( String name ) {
			string[] parts = name.Split( '.' );
			string chName = parts[parts.Length - 1];

			StringBuilder srNameBuilder = new StringBuilder();
			for (int i = 0; i < parts.Length - 1; i++)
				srNameBuilder.Append( parts[i] ).Append( "." );

			if (srNameBuilder.Length > 1)
				srNameBuilder.Length -= 1;

			string srName = srNameBuilder.ToString();
			if (string.IsNullOrWhiteSpace( srName ))
				srName = defaultSeriesKey;

			ISeries series = dataset[srName];

			return series.Channels[chName];
		}

		public static DataSetChannelRef GetChannelRef( string path ) {
			string[] parts = path.Split( '.' );
			string chName = parts[parts.Length - 1];
			int chIdx;
			if (int.TryParse( chName, out chIdx )) {

				StringBuilder srNameBuilder = new StringBuilder();
				for (int i = 0; i < parts.Length - 1; i++)
					srNameBuilder.Append( parts[i] ).Append( "." );

				string pathId = srNameBuilder.ToString();

				//Remove trailing '.'
				if (srNameBuilder.Length > 1)
					srNameBuilder.Length -= 1;

				string srName = srNameBuilder.ToString();
				if (string.IsNullOrWhiteSpace( srName ))
					srName = defaultSeriesKey;

				ISeries series = dataset[srName];

				return new DataSetChannelRef( pathId + series.Channels[chIdx].Name );
			} else
				return new DataSetChannelRef( path );
		}

		public static IEnumerable<string> GetChannelNames() {
			List<string> ret = new List<string>();
			foreach (KeyValuePair<string, ISeries> p in dataset) {
				string seriesKey = p.Key;
				foreach (IChannel ch in dataset[seriesKey].Channels)
					ret.Add( String.Format( "{0}.{1}", seriesKey, ch.Name ) );
			}
			return ret;
		}

		private class DatasetChannelsObject : IDatasetChannelsObject {
			public const string PROP_PreferredChannels = "PreferredChannels";

			private bool _usePreferredChannels = true;
			public bool UsePreferredChannels {
				get { return _preferredChannels.Count > 0 && _usePreferredChannels; }
				set { _usePreferredChannels = value; RaisePropertyChanged( PROP_PreferredChannels ); }
			}

			public ObservableCollection<IChannelRef> _allChannels = new ObservableCollection<IChannelRef>();
			public ReadOnlyObservableCollection<IChannelRef> AllChannels {
				get { return new ReadOnlyObservableCollection<IChannelRef>( _allChannels ); }
			}

			public ObservableCollection<IChannelRef> _preferredChannels = new ObservableCollection<IChannelRef>();
			public ReadOnlyObservableCollection<IChannelRef> PreferredChannels {
				get {
					return (UsePreferredChannels && _preferredChannels != null)
						? new ReadOnlyObservableCollection<IChannelRef>( _preferredChannels )
						: new ReadOnlyObservableCollection<IChannelRef>( _allChannels );
				}
			}

			public event PropertyChangedEventHandler PropertyChanged;
			private void RaisePropertyChanged( string propertyName ) { if (PropertyChanged != null)PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) ); }
		}
		public static ISeries GetSeries( IChannelRef channelRef ) {
			ISeries series;
			int idxSep = channelRef.Name.LastIndexOf( '.' );

			if (idxSep < 0)
				series = dataset[defaultSeriesKey];
			else
				series = dataset[channelRef.Name.Substring( 0, channelRef.Name.LastIndexOf( '.' ) )];

			return series;
		}
	}
	public interface IDatasetChannelsObject : INotifyPropertyChanged {

		bool UsePreferredChannels { get; set; }

		ReadOnlyObservableCollection<IChannelRef> AllChannels { get; }

		ReadOnlyObservableCollection<IChannelRef> PreferredChannels { get; }
	}
	public interface IChannelRef : ICloneable {
		IChannel Channel { get; }
		string Name { get; }
		bool IsPreferred { get; set; }
	}
	public class DataSetChannelRef : IChannelRef {
		private readonly string path;
		public DataSetChannelRef( string path ) {
			this.path = path;
			Name = path;
		}

		public IChannel Channel { get { return DataSet.GetChannel( path ); } }

		public string Name { get; private set; }

		public bool IsPreferred {
			get { return DataSet.IsPreferred( this ); }
			set { DataSet.SetPreferred( this, value ); }
		}

		public override bool Equals( object obj ) {
			if (!(obj is IChannelRef)) return false;
			IChannel self = Channel;
			IChannel othr = ((IChannelRef)obj).Channel;
			return self == othr || (self != null && self.Equals( othr ));
		}

		public object Clone() {
			return DataSet.GetChannelRef( path );
		}
	}
	class VirtualisingSeries : AbstractSeriesTransform {
		class VirtualisingChannelCollection : IChannelCollection {
			//TODO: Replace this group of collections with a single tri-partite map (index-name-channel)
			private Dictionary<string, IChannel> _nameMap = new Dictionary<string, IChannel>();
			private List<IChannel> _idxMap = new List<IChannel>();
			public IChannelCollection Source { get; set; }
			public IChannel IndexingChannel;
			public int Count { get { return ((Source != null) ? Source.Count : 0) + _idxMap.Count; } }

			public IChannel this[string name] {
				get { return Source[name] ?? _nameMap[name]; }
			}

			public IChannel this[int idx] {
				get { return (idx < Source.Count) ? Source[idx] : (idx - Source.Count < _idxMap.Count) ? _idxMap[idx - Source.Count] : IndexingChannel; }
			}

			public VirtualisingChannelCollection() {
				IndexingChannel = new VirtualChannel( "#INDX", ( v ) => GetIndex() );
			}

			public void AddChannel( VirtualChannel channel ) {
				_nameMap.Add( channel.Name, channel );
				_idxMap.Add( channel );
			}
			public void InsertChannel( int idx, VirtualChannel channel ) {
				_nameMap.Add( channel.Name, channel );
				_idxMap.Insert( idx, channel );
			}
			public void ReplaceChannel( int idx, VirtualChannel channel ) {
				_nameMap.Remove( this[idx].Name );
				_idxMap[idx] = channel;
				_nameMap.Add( channel.Name, channel );
			}

			public int GetIndex() {
				return 0;
			}

			public int IndexOf( IChannel channel ) {
				int ret = Source.IndexOf( channel );
				return (ret == -1) ? Source.Count + _idxMap.IndexOf( channel ) : ret;
			}

			public IEnumerator<IChannel> GetEnumerator() {
				foreach (IChannel ch in Source)
					yield return ch;
				foreach (IChannel ch in _idxMap)
					yield return ch;
			}

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
				return GetEnumerator();
			}
		}

		private VirtualisingChannelCollection vcc = new VirtualisingChannelCollection();
		public override IChannelCollection Channels {
			get {
				return vcc;
			}
		}

		public override int Dimensions {
			get {
				return vcc.Count;
			}
		}

		protected override void OnSourceChanged( ISeries oldValue, ISeries newValue ) {
			base.OnSourceChanged( oldValue, newValue );
			vcc.Source = newValue.Channels;
		}

		public VirtualisingSeries( ISeries source ) {
			Source = source;
			vcc = new VirtualisingChannelCollection() { Source = source.Channels };
		}

		public void AddChannel( VirtualChannel ch ) {
			vcc.AddChannel( ch );
			CalcLimits();
		}

		public override IEnumerator<Vector> GetEnumerator() {
			foreach (Vector v in Source) {
				Vector ret = v.Top( vcc.Count );
				for (int i = Source.Channels.Count; i < vcc.Count; i++) {
					ret[i] = ((VirtualChannel)vcc[i]).GetValue( v );
				}
				yield return ret;
			}
		}
	}
	class VirtualChannel : IChannel {
		private readonly VirtualChannelCallback callback;

		public string Name { get; private set; }

		public Limits Limits { get; set; }

		public VirtualChannel( string name, VirtualChannelCallback callback, Limits? limits = null ) {
			this.callback = callback;
			this.Name = name;
		}

		public double GetValue( Vector source ) {
			return callback( source );
		}

	}
}
